package method3

// 计算方法各个学科前 5 % 的文章

import (
	"bufio"
	"encoding/csv"
	"encoding/json"
	"fmt"
	"io"
	"log"
	"os"
	"sort"
	"strconv"
	"strings"
	"sync"
	"sync/atomic"
	"time"

	"github.com/emirpasic/gods/sets/hashset"
)

type linksInObj struct {
	Id   string
	Lset *hashset.Set
}

type linksInArray []linksInObj

func (s linksInArray) Len() int           { return len(s) }
func (s linksInArray) Less(i, j int) bool { return s[i].Lset.Size() > s[j].Lset.Size() }
func (s linksInArray) Swap(i, j int)      { s[i], s[j] = s[j], s[i] }

/**
 * @description: 读取全部linksin
 * @param subjectFile 学科文件名
 * @return subjectLiksin 学科的 link sin map
 * @return lias 学科的 linsin 递减排序数组
 */
func ReadSubjectLinsin(subjectFile string) (subjectLiksin *map[string]*hashset.Set, lias *linksInArray) {
	// 获取linksin文件,转换到数据set
	subjectLiksin = new(map[string]*hashset.Set)
	*subjectLiksin = make(map[string]*hashset.Set)
	fi, err := os.Open("/home/ni/data/mag/referenced/delete_noref_v2/" + subjectFile)
	if err != nil {
		fmt.Printf("Error: %s\n", err)
		return
	}
	defer fi.Close()
	start_time := time.Now()
	br := bufio.NewReader(fi)
	for {
		a, c := br.ReadString('\n')
		if c == io.EOF {
			break
		}
		strs := strings.Split(string(a), "\t")

		if _, ok := (*subjectLiksin)[strs[0]]; ok {
			(*subjectLiksin)[strs[0]].Add(strs[2])
		} else {
			linksSet := hashset.New(strs[2])
			(*subjectLiksin)[strs[0]] = linksSet
		}
	}
	end_time := time.Now()
	fmt.Println("读linksin学科耗时", end_time.Sub(start_time))

	// 将数据转换到 struct 准备排序
	lias = new(linksInArray)
	*lias = make(linksInArray, 0)
	for k, v := range *subjectLiksin {
		*lias = append(*lias, linksInObj{k, v})
	}
	// 从大到小排序
	sort.Sort(linksInArray(*lias))
	// fmt.Println((*lias)[0:5])
	return
}

/**
 * @description: 读取学科的linksout 文件,保留过滤表内的文章
 * @param subjectFile 学科文件名
 * @param dataRangeSet 需要保留的学科Set
 * @return subjectSet 学科文章集合
 * @return subjectOutSet 学科out的文章集合
 */
func ReadSubjectLinsout(subjectFile string, dataRangeSet *hashset.Set) (subjectOutSet *hashset.Set) {

	subjectOutSet = hashset.New()

	fi, err := os.Open("/home/ni/data/mag/reference/delete_noref_v2/" + subjectFile)
	if err != nil {
		fmt.Printf("Error: %s\n", err)
		return
	}
	defer fi.Close()

	start_time := time.Now()
	br := bufio.NewReader(fi)
	for {
		a, c := br.ReadString('\n')
		if c == io.EOF {
			break
		}
		strs := strings.Split(string(a), "\t")
		if dataRangeSet.Contains(strs[0]) {
			subjectOutSet.Add(strs[2])
		}
	}
	end_time := time.Now()
	fmt.Println("读linksout学科耗时", end_time.Sub(start_time))
	return
}

type NewChanObj struct {
	Key string
	S   *[]string
}

/**
 * @description: 统计所有集合的节点情况，全部加载到内存，得到linksin,linksout,空间换时间
 * @return linksinRef 学科 linksin 的map集合
 */
func ReadAllLinksChanRef() (linksinRef *map[string]*hashset.Set) {
	linksinRef = new(map[string]*hashset.Set)
	*linksinRef = make(map[string]*hashset.Set)

	fi, err := os.Open("/home/ni/data/mag/all_refed_authors_data.txt")
	if err != nil {
		fmt.Printf("Error: %s\n", err)
		return
	}
	defer fi.Close()
	start_time := time.Now()
	br := bufio.NewReader(fi)

	ch := make(chan string, 1000)
	chOut := make(chan NewChanObj, 1000)
	const threaCount = 20
	var wg = sync.WaitGroup{}
	var wgOut = sync.WaitGroup{}
	wg.Add(threaCount)
	wgOut.Add(1)
	// 处理数据的协程
	for i := 0; i < threaCount; i++ {
		go func() {
			for a := range ch {
				strs := strings.Split(a, "\t")
				linksoutArray := []string{}
				if len(strs) >= 3 {
					err := json.Unmarshal([]byte(strs[2]), &linksoutArray)
					if err != nil {
						fmt.Printf("Error: %s\n", err)
						fmt.Printf("%+v\n", string(a))
						return
					}
				} else {
					fmt.Println("strs", strs)
				}
				if len(linksoutArray) > 0 {
					co := NewChanObj{Key: strs[0], S: &linksoutArray}
					chOut <- co
				}
			}
			wg.Done()
		}()
	}
	// linksout保存到字典,linksin保存到字典
	go func() {
		for chobj := range chOut {
			// save linksin
			for _, sid := range *chobj.S {
				if _, ok := (*linksinRef)[sid]; ok {
					// 存在
					(*linksinRef)[sid].Add(chobj.Key)
				} else {
					// 不存在
					(*linksinRef)[sid] = hashset.New(chobj.Key)
				}
			}
		}
		wgOut.Done()
	}()

	// 加载到队列
	// count := 0
	for {
		a, c := br.ReadString('\n')
		if c == io.EOF {
			break
		}
		ch <- a
		// count += 1
		// if count > 100000 {
		// 	break
		// }
	}

	close(ch)
	wg.Wait()
	close(chOut)
	wgOut.Wait()
	end_time := time.Now()
	fmt.Println("read all links:", end_time.Sub(start_time))
	return
}

type ChanObj struct {
	Key string
	S   *[]string
}

/**
 * @description: 统计所有集合的节点情况，全部加载到内存，空间换时间，使用管道 协程，加快解析速度
 * @return {*}
 */
func ReadAllLinksChan() (allRef *map[string]*[]string) {
	var wg = sync.WaitGroup{}
	var wgOut = sync.WaitGroup{}
	allRef = new(map[string]*[]string)
	*allRef = make(map[string]*[]string)
	// *allRef = map[string]*[]string

	fi, err := os.Open("/home/ni/data/mag/all_refed_authors_data.txt")
	// fi, err := os.Open("test/ta.txt")
	if err != nil {
		fmt.Printf("Error: %s\n", err)
		return
	}
	defer fi.Close()
	start_time := time.Now()
	// br := bufio.NewReaderSize(fi, 50000)
	br := bufio.NewReader(fi)

	ch := make(chan string, 1000)
	chOut := make(chan ChanObj, 1000)
	const threaCount = 20
	wg.Add(threaCount)
	wgOut.Add(1)
	for i := 0; i < threaCount; i++ {
		// 解析 json
		go func() {
			for a := range ch {
				strs := strings.Split(a, "\t")
				linksoutArray := []string{}
				if len(strs) >= 3 {
					err := json.Unmarshal([]byte(strs[2]), &linksoutArray)
					if err != nil {
						fmt.Printf("Error: %s\n", err)
						fmt.Printf("%+v\n", string(a))
						return
					}
				} else {
					fmt.Println("strs", strs)
				}
				if len(linksoutArray) > 0 {
					co := ChanObj{Key: strs[0], S: &linksoutArray}
					chOut <- co
				}
			}
			wg.Done()
		}()
	}
	// 保存到字典
	go func() {
		for chobj := range chOut {
			(*allRef)[chobj.Key] = chobj.S
		}
		wgOut.Done()
	}()

	for {
		a, c := br.ReadString('\n')
		if c == io.EOF {
			break
		}
		ch <- a
	}
	close(ch)
	wg.Wait()
	close(chOut)
	wgOut.Wait()
	end_time := time.Now()
	fmt.Println("read all links:", end_time.Sub(start_time))
	return
}

/**
 * @description: 计算处理程序，使用map，单线程计算
 * @param {*}
 * @return {*}
 */
func HandleChan(allRef *map[string]*[]string, subjectSet, subjectOutSet *hashset.Set) {

	var (
		ni, nj, nk int
	)

	start_time := time.Now()
	for ks, va := range *allRef {

		// 在 subject 内，排除计算
		if subjectSet.Contains(ks) {
			continue
		}

		flagNi := false
		flagNk := false

		for _, id := range *va {
			if subjectSet.Contains(id) {
				flagNi = true
				break
			}
		}

		for _, id := range *va {
			if subjectOutSet.Contains(id) {
				flagNk = true
				break
			}
		}

		if flagNi == true && flagNk == true {
			nj += 1
		} else if flagNk == true {
			nk += 1
		} else if flagNi == true {
			ni += 1
		}
	}

	end_time := time.Now()
	fmt.Println(end_time.Sub(start_time))
	fmt.Println(ni, nj, nk)
	fmt.Println((float64(ni) - float64(nj)) / (float64(ni) + float64(nj) + float64(nk)))
}

type RetChan struct {
	Ks string
	Va *hashset.Set
}

/**
 * @description: 协程加速处理计算处理程序
 */
func HandleChanThead(linksinRef *map[string]*hashset.Set, subjectDataRangeSet, subjectOutSet *hashset.Set, subjectLinksin *map[string]*hashset.Set) (ni, nj, nk int) {

	start_time := time.Now()
	wg := sync.WaitGroup{}
	wg.Add(2)
	// 找到所有的linksout 的 linksin
	subject_linksout_linksin_set := hashset.New()
	go func() {
		for _, id := range subjectOutSet.Values() {
			// 有 linksin 才加入 set
			if _, ok := (*linksinRef)[id.(string)]; ok {
				subject_linksout_linksin_set.Add((*linksinRef)[id.(string)].Values()...)
			}
		}
		wg.Done()
	}()
	// 找到学科的 linksin
	subject_linksin_set := hashset.New()
	go func() {
		for k, v := range *subjectLinksin {
			if subjectDataRangeSet.Contains(k) {
				subject_linksin_set.Add(v.Values()...)
			}
		}
		wg.Done()
	}()
	wg.Wait()
	ni, nj, nk = Cal_njik(subject_linksout_linksin_set, subject_linksin_set)

	end_time := time.Now()
	fmt.Println("计数耗时", end_time.Sub(start_time))
	fmt.Println(ni, nj, nk, (float64(ni)-float64(nj))/(float64(ni)+float64(nj)+float64(nk)))
	return
}

func HandalTest() {
	// subjectSet, subjectOutSet := ReadSubjectLinsout("Logic.txt")
	// fmt.Println(subjectSet.Size())
	// fmt.Println(subjectOutSet.Size())

	ReadSubjectLinsin("Logic.txt")

}

var ThreadMax int64

/**
 * @description:处理测试程序,
 */
func HandalMain() {
	linksinRef := ReadAllLinksChanRef()
	// AllSubjects := []string{"Number theory.txt", "Logic.txt", "Genome editing.txt", "Quantum computing.txt", "Experimental physics.txt", "Deep learning.txt", "Operating system.txt", "Genetic engineering.txt", "Literature.txt", "Geometry.txt", "Industrial engineering.txt", "Applied physics.txt", "Applied mathematics.txt", "Algebra.txt", "Cognitive science.txt", "Theoretical physics.txt", "Philosophy.txt", "Linguistics.txt", "Biological engineering.txt", "Anthropology.txt", "Computer engineering.txt", "Civil engineering.txt", "History.txt", "Theoretical computer science.txt", "Discrete mathematics.txt", "Political science.txt", "Environmental engineering.txt", "Mechanical engineering.txt", "Earth science.txt", "Geography.txt", "Machine learning.txt", "Electrical engineering.txt", "Chemical engineering.txt", "Environmental science.txt", "Sociology.txt", "Neuroscience.txt", "Artificial intelligence.txt", "Geology.txt", "Economics.txt", "Mathematics.txt", "Physics.txt", "Psychology.txt", "Materials science.txt", "Computer science.txt", "Engineering disciplines.txt", "Chemistry.txt", "Biology.txt", "Medicine.txt"}
	AllSubjects := []string{"Computer engineering.txt", "Civil engineering.txt", "History.txt", "Theoretical computer science.txt", "Discrete mathematics.txt", "Political science.txt", "Environmental engineering.txt", "Mechanical engineering.txt", "Earth science.txt", "Geography.txt", "Machine learning.txt", "Electrical engineering.txt", "Chemical engineering.txt", "Environmental science.txt", "Sociology.txt", "Neuroscience.txt", "Artificial intelligence.txt", "Geology.txt", "Economics.txt", "Mathematics.txt", "Physics.txt", "Psychology.txt", "Materials science.txt", "Computer science.txt", "Engineering disciplines.txt", "Chemistry.txt", "Biology.txt", "Medicine.txt"}

	wg1 := sync.WaitGroup{}
	wg1.Add(len(AllSubjects))
	// 输出
	wg2 := sync.WaitGroup{}
	wg2.Add(1)
	chout := make(chan []string, 1000)
	go func() {
		for line := range chout {
			// fmt.Println(line)
			WriteCsvLine("all_subject_size_trand.csv", &line)
		}
		wg2.Done()
	}()
	// 输入

	ThreadMax = 0

	for _, subjectName := range AllSubjects {
		ThreadMax += 1
		go func(SN string) {
			subjectLinksin, lias := ReadSubjectLinsin(SN)
			for i := 1000; i <= 100000; i += 1000 {
				// 排名内的学科
				subjectDataRangeSet := hashset.New()
				for _, obj := range (*lias)[0:i] {
					subjectDataRangeSet.Add(obj.Id)
				}

				subjectOutSet := ReadSubjectLinsout(SN, subjectDataRangeSet)
				ni, nj, nk := HandleChanThead(linksinRef, subjectDataRangeSet, subjectOutSet, subjectLinksin)
				csvLine := []string{SN, strconv.Itoa(ni), strconv.Itoa(nj), strconv.Itoa(nk), strconv.Itoa(i), strconv.Itoa(subjectDataRangeSet.Size())}
				chout <- csvLine
				// 超过长度，则退出
				if len(*lias) < i {
					break
				}
			}
			atomic.AddInt64(&ThreadMax, -1)
			wg1.Done()
		}(subjectName)
		// 小于某个值，则延迟
		for {
			if ThreadMax <= 5 {
				fmt.Println("yansho++++++++++++++++++++++++++++++++")
				break
			}
			<-time.After(20 * time.Second)
		}
	}
	wg1.Wait()
	close(chout)
	wg2.Wait()
}

/**
 * @description: 小规模写入数据
 * @param {string} path
 * @param {*[]string} data
 * @return {*}
 */
func WriteCsvLine(path string, data *[]string) {
	//OpenFile读取文件，不存在时则创建，使用追加模式
	File, err := os.OpenFile(path, os.O_RDWR|os.O_APPEND|os.O_CREATE, 0666)
	if err != nil {
		log.Println("文件打开失败！")
	}
	defer File.Close()

	//创建写入接口
	WriterCsv := csv.NewWriter(File)
	err1 := WriterCsv.Write(*data)
	if err1 != nil {
		log.Println("WriterCsv写入文件失败")
	}
	WriterCsv.Flush() //刷新，不刷新是无法写入的
}

// 计算差
func Cal_njik(a, b *hashset.Set) (ni, nj, nk int) {
	for _, v := range b.Values() {
		if !a.Contains(v.(string)) {
			ni += 1
		}
	}
	for _, v := range a.Values() {
		if !b.Contains(v.(string)) {
			nk += 1
		}
	}
	nj = (b.Size() + a.Size() - ni - nj) / 2
	return
}
